package com.dale.shiro;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.dale.common.lang.Result;
import com.dale.util.JwtUtils;
import com.dale.util.ShiroUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwt;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * JwtFilter
 * 有处理跨域问题 preHandle
 * 因为我们是前后端分析项目，项目中除了需要跨域全局配置之外，我们再拦截器中也需要提供跨域支持。
 * 这样，拦截器才不会在进入Controller之前就被限制了。
 */


/**
 * 继承了AuthenticatingFilter
 * 必须重写onAccessDenied、onLoginFailure
 * 然后又重写了createToken（）非必须  原生是UsernamePasswordToken
 */
@Component
public class JwtFilter extends AuthenticatingFilter {

    //点开AuthenticatingFilter   里面会交给shiro执行登录  通过jwtToken


    @Autowired
    JwtUtils jwtUtils;

    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String jwt = request.getHeader("Authorization");
        if (StringUtils.isEmpty(jwt)) {
            return null;
        }

        return new JwtToken(jwt);
    }




    /**
     * 是否是拒绝登录
     * 没有登录的情况下会走此方法
     * 还有一个isAccessAllowed
     * 如果isAccessAllowed方法返回True，则不会再调用onAccessDenied方法，如果isAccessAllowed方法返回Flase,则会继续调用onAccessDenied方法。
     * 而onAccessDenied方法里面则是具体执行登陆的地方。由于我们已经登陆，所以此方法就会返回True(filter放行),所以上面的onPreHandle方法里面的onAccessDenied方法就不会被执行。
     * ————————————————
     * 参考连接：https://blog.csdn.net/qq_40202111/article/details/106397360
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

        //第二步 走这（即使我登录了）
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String jwt = request.getHeader("Authorization");
        if (StringUtils.isEmpty(jwt)) {
            return true;    //没有jwt 直接访问  放行（这里要理解 虽然放行 但是前台获取不到jwt 就表示没有登录  前台就能拦截
            // 而后台自然也可以通过  @RequiresAuthentication 认证(是否登录) "authc"  来限制是否访问接口  错误则抛出未授权）
            //"authc"会先去调用isAccessAllowed
        } else {

            //有 则 校验jwt
            Claims claim = jwtUtils.getClaimByToken(jwt);

            try {
                if (claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {
//                HttpServletResponse response = (HttpServletResponse) servletResponse;
//
//                response.reset();
//                response.setContentType("");

                    //发现全局异常不起着作用
                    throw new ExpiredCredentialsException("token已失效，请重新登录");

                }

            } catch (ExpiredCredentialsException e) {
//                System.out.println("这里捕获一场");
                servletResponse.setCharacterEncoding("UTF-8");
                servletResponse.setContentType("application/json;charset=UTF-8");
                PrintWriter writer = servletResponse.getWriter();
                writer.write(JSONUtil.toJsonStr(Result.fail(300,"登陆过期 请重新登录!",null)));


                return false;
            }
        }

        // 执行登录    父类方法
        return executeLogin(servletRequest, servletResponse);

    }

//    @Override
//    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
//
//        //第二步 走这（即使我登录了）
//        HttpServletRequest request = (HttpServletRequest) servletRequest;
//        String jwt = request.getHeader("Authorization");
//        if (((HttpServletRequest) request).getHeader("Token") != null) {
//            //如果存在，则进入 executeLogin 方法执行登入，检查 token 是否正确
//            try {
//                executeLogin(servletRequest, servletResponse);
//                return true;
//            } catch (Exception e) {
//                //token 错误
//                PrintWriter writer = servletResponse.getWriter();
//                writer.write(JSONUtil.toJsonStr(Result.fail("relogin please!")));
//            }
//        }
//        //如果请求头不存在 Token，则可能是执行登陆操作或者是游客状态访问，无需检查 token，直接返回 true
//        return true;
//    }

    //上面登录executeLogin 失败的化则在这里处理  本来可以直接return false  但是为了让前端知道。。。
//    @Override
//    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
//
//        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//
//        Throwable throwable = e.getCause() == null ? e : e.getCause();
//        Result result = Result.fail(throwable.getMessage());
//        String json = JSONUtil.toJsonStr(result);
//
//        try {
//            httpServletResponse.getWriter().print(json);
//        } catch (IOException ioException) {
//
//        }
//        return false;
//    }


    //解决controller前 跨域问题   其实往往不需要？？
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        //第一步会在这里 被拦截  解决跨域
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个OPTIONS请求，这里我们给OPTIONS请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
            return false;
        }

        return super.preHandle(request, response);


    }
}
